一文读懂Go的net/http标准库 |
您所在的位置:网站首页 › go rsa加密 › 一文读懂Go的net/http标准库 |
在进行Web应用开发的时候,使用成熟并且复杂的Web应用框架通常会使开发变得更加迅速和简便,但这也意味着开发者必须接受框架自身的一套约定和模式。虽然很多框架都认为自己提供的约定和模式是最佳实践(best practice),但是如果开发者没有正确地理解这些最佳实践,那么对最佳实践的应用就可能会发展为货物崇拜编程(cargo cult programming):开发者如果不了解这些约定和模式的用法,就可能会在不必要甚至有害的情况下盲目地使用它们。 货物崇拜编程 第二次世界大战期间,盟军为了对战事提供支援,在太平洋的多个岛屿上设立了空军基地,以空投的方式向部队以及支援部队的岛民投送了大量生活用品以及军事设备,从而极大地改善了部队以及岛民的生活,岛民也因此第一次看到了人工生产的衣物、罐头食品以及其他物品。在战争结束之后,这些空军基地便被废弃了,货物空投自然也停止了。此时,岛民做了一件非常符合其本性的事情——他们把自己打扮成空管员、士兵以及水手,使用机场上的指挥棒挥舞着着陆信号,进行地面阅兵演习,试图让飞机继续空投货物,货物崇拜一词也因此而诞生。 尽管货物崇拜程序员并没有像岛民一样挥舞指挥棒,但他们却大量地复制和粘贴从StackOverflow这类网站上找来的代码,这些代码虽然能够运行,但是他们却对这些代码的工作原理一点也不了解。这样做的结果是,他们通常无法扩展和修改这些代码。与此类似,货物崇拜程序员通常会在既不了解框架为什么使用特定的模式或约定,也不知道框架做了何种取舍的情况下,盲目地使用Web框架。 举个例子来说,因为HTTP是一种无连接协议(connection-less protocol),通过这种协议发送给服务器的请求对服务器之前处理过的请求一无所知,所以应用程序才会以cookie的方式在客户端实现数据持久化,并以会话的方式在服务器上实现数据持久化,而不了解这一点的人是很难理解为什么要在不同连接之间使用cookie和会话实现信息持久化的。为了降低使用cookie和会话带来的复杂性,Web应用框架通常都会提供一个统一的接口(uniform interface),用于在连接之间实现持久化。这样做的结果是,很多新手程序员都会想当然地假设在连接之间进行持久化唯一要做的就是使用框架提供的接口。但是由于这类接口通常都是根据框架自身的习惯制定的,因此不同框架提供的接口可能会有所不同。更糟糕的是,不同的框架可能会提供一些名字相同的接口,但是这些同名接口之间的实现却又千差万别、各不相同,因此给开发者带来不必要的困惑。通过这个例子可以看出,使用框架进行Web应用开发意味着将框架与应用进行绑定,之后无论是将应用迁移至另一个框架,还是对应用进行扩展,又或者为应用添加新的特性,都需要对框架本身有深入的了解,在某些情况下可能还需要对框架进行定制。 本文的目的并不是让大家抛弃框架、约定和模式——一个好的框架通常是快速构建可扩展且健壮的Web应用的最好方法,但理解那些隐藏在框架之下的底层概念和基础设施也是非常重要的。只要对框架的实现原理有了正确的认识,我们就可以更加清晰地了解到这些约定和模式是如何形成的,从而避免陷阱、理清思路,不再盲目地使用模式。 对Go语言来说,隐藏在框架之下的通常是net/http和html/template这两个标准库,如图1所示,net/http标准库可以分为客户端和服务器两个部分,库中的结构和函数有些只支持客户端和服务器这两者中的一个,而有些则同时支持客户端和服务器: Client、Response、Header、Request和Cookie对客户端进行支持;Server、ServeMux、Handler/HandleFunc、ResponseWriter、Header、Request和Cookie则对服务器进行支持。本文接下来将会展示如何把net/http标准库用作服务器以及如何使用Go语言接收客户端发送的HTTP请求。在之后的第4章,我们还会继续使用net/http标准库,但焦点会放在如何处理请求上面。 在Go Web编程中,我们主要关注的是如何使用net/http标准库的服务器功能而非客户端功能。 图1 net/http标准库的各个组成部分 使用Go构建服务器 如图2所示,通过net/http标准库,我们可以启动一个HTTP服务器,然后让这个服务器接收请求并向请求返回响应。除此之外,net/http标准库还提供了一个连接多路复用器(multiplexer)的接口以及一个默认的多路复用器。 图2 通过Go服务器处理请求 Go Web服务器跟其他编程语言里面的绝大多数标准库不一样,Go提供了一系列用于创建Web服务器的标准库。正如代码清单3-1所示,创建一个服务器的步骤非常简单,只要调用ListenAndServe并传入网络地址以及负责处理请求的处理器(handler)作为参数就可以了。如果网络地址参数为空字符串,那么服务器默认使用80端口进行网络连接;如果处理器参数为nil,那么服务器将使用默认的多路复用器DefaultServeMux。 代码清单3-1 最简单的Web服务器 package main import ( "net/http" ) func main() { http.ListenAndServe("", nil) } 用户除了可以通过ListenAndServe的参数对服务器的网络地址和处理器进行配置之外,还可以通过Server结构对服务器进行更详细的配置,其中包括为请求读取操作设置超时时间、为响应写入操作设置超时时间以及为Server结构设置错误日志记录器等。 代码清单3-2和代码清单3-1的作用基本上是相同的,它们之间的唯一区别在于代码清单3-2可以通过Server结构对服务器进行更多的配置。 代码清单3-2 带有附加配置的Web服务器 package main
import ( "net/http" )
func main() { server := http.Server{ Addr: "127.0.0.1:8080", Handler: nil, } server.ListenAndServe() } 代码清单3-3展示了Server结构所有可选的配置选项。 代码清单3-3 Server结构的配置选项 type Server struct { Addr string Handler Handler ReadTimeout time.Duration WriteTimeout time.Duration MaxHeaderBytes int TLSConfig *tls.Config TLSNextProto map[string]func(*Server, *tls.Conn, Handler) ConnState func(net.Conn, ConnState) ErrorLog *log.Logger } 通过HTTPS提供服务 当客户端和服务器需要共享密码或者信用卡信息这样的私密信息时,大多数网站都会使用HTTPS对客户端和服务器之间的通信进行加密和保护。在一些情况下,这种保护甚至是强制性的。比如说,如果一个网站提供了信用卡支付功能,那么按照支付卡行业数据安全标准(Payment Card Industry Data Security Standard),这个网站就必须对客户端和服务器之间的通信进行加密。像Gmail和Facebook这样带有隐私性质的网站甚至在整个网站上都启用了HTTPS。如果你打算开发一个网站,而这个网站又需要提供用户登录功能,那么你也需要在这个网站上启用HTTPS。 HTTPS实际上就是将HTTP通信放到SSL之上进行。通过使用ListenAndServeTLS函数,我们可以让之前展示过的简单Web应用也提供HTTPS服务,代码清单3-4展示了具体的实现代码。 代码清单3-4 通过HTTPS提供服务 package main
import ( "net/http" )
func main() { server := http.Server{ Addr: "127.0.0.1:8080", Handler: nil, } server.ListenAndServeTLS("cert.pem", "key.pem") } 这段代码中的cert.pem文件是SSL证书,而key.pem则是服务器的私钥(private key)。在生产环境中使用的SSL证书需要通过VeriSign、Thawte或者Comodo SSL这样的CA取得,但如果是出于测试目的才使用证书和私钥,那么使用自行生成的证书就可以了。生成证书的办法有很多种,其中一种就是使用Go标准库中的crypto包群(library group)。 SSL、TLS和HTTPS SSL(Secure Socket Layer,安全套接字层)是一种通过公钥基础设施(Public Key Infrastructure,PKI)为通信双方提供数据加密和身份验证的协议,其中通信的双方通常是客户端和服务器。SSL最初由Netscape公司开发,之后由IETF(Internet Engineering Task Force,互联网工程任务组)接手并将其改名为TLS(Transport Layer Security,传输层安全协议)。HTTPS,即SSL之上的HTTP,实际上就是在SSL/TLS连接的上层进行HTTP通信。 HTTPS需要使用SSL/TLS证书来实现数据加密以及身份验证。SSL证书存储在服务器之上,它是一种使用X.509格式进行格式化的数据,这些数据包含了公钥以及其他一些相关信息。为了保证证书的可靠性,证书一般由证书分发机构(Certificate Authority,CA)签发。服务器在接收到客户端发送的请求之后,会将证书和响应一并返回给客户端,而客户端在确认证书的真实性之后,就会生成一个随机密钥(random key),并使用证书中的公钥对随机密钥进行加密,此次加密产生的对称密钥(symmetric key)就是客户端和服务器在进行通信时,负责对通信实施加密的实际密钥(actual key)。 虽然我们不会在生产环境中使用自行生成的证书和私钥,但了解SSL证书和私钥的生成方法,并学会如何在开发和测试的过程中使用证书和私钥,也是一件非常有意义的事情。代码清单3-5展示了生成SSL证书以及服务器私钥的具体代码。 代码清单3-5 生成个人使用的SSL证书以及服务器私钥 package main
import ( "crypto/rand" "crypto/rsa" "crypto/x509" "crypto/x509/pkix" "encoding/pem" "math/big" "net" "os" "time" ) func main() { max := new(big.Int).Lsh(big.NewInt(1), 128) serialNumber, _ := rand.Int(rand.Reader, max) subject := pkix.Name{ Organization: []string{"Manning Publications Co."}, OrganizationalUnit: []string{"Books"}, CommonName: "Go Web Programming", }
template := x509.Certificate{ SerialNumber: serialNumber, Subject: subject, NotBefore: time.Now(), NotAfter: time.Now().Add(365 * 24 * time.Hour), KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature, ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, IPAddresses: []net.IP{net.ParseIP("127.0.0.1")}, }
pk, _ := rsa.GenerateKey(rand.Reader, 2048)
derBytes, _ := x509.CreateCertificate(rand.Reader, &template, ➥&template, &pk.PublicKey, pk) certOut, _ := os.Create("cert.pem") pem.Encode(certOut, &pem.Block{Type: "CERTIFICATE", Bytes: derBytes}) certOut.Close()
keyOut, _ := os.Create("key.pem") pem.Encode(keyOut, &pem.Block{Type: "RSA PRIVATE KEY", Bytes: ➥x509.MarshalPKCS1PrivateKey(pk)}) keyOut.Close() } 生成SSL证书和密钥的步骤并不是特别复杂。因为SSL证书实际上就是一个将扩展密钥用法(extended key usage)设置成了服务器身份验证操作的X.509证书,所以程序在生成证书时使用了crypto/x509标准库。此外,因为创建证书需要用到私钥,所以程序在使用私钥成功创建证书之后,会将私钥单独保存在一个存放服务器私钥的文件里面。 让我们来仔细分析一下代码清单3-5中的主要代码吧。首先,程序使用一个Certificate结构来对证书进行配置: template := x509.Certificate{ SerialNumber: serialNumber, Subject: subject, NotBefore: time.Now(), NotAfter: time.Now().Add(365*24*time.Hour), KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature, ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, IPAddresses: []net.IP{net.ParseIP("127.0.0.1")}, } 结构中的证书序列号(SerialNumber)用于记录由CA分发的唯一号码,为了能让我们的Web应用运行起来,程序在这里生成了一个非常长的随机整数来作为证书序列号。之后,程序创建了一个专有名称(distinguished name),并将它设置成了证书的标题(subject)。此外,程序还将证书的有效期设置成了一年,而结构中KeyUsage字段和ExtKeyUsage字段的值则表明了这个X.509证书是用于进行服务器身份验证操作的。最后,程序将证书设置成了只能在IP地址127.0.0.1之上运行。 SSL证书 X.509是国际电信联盟电信标准化部门(ITU-T)为公钥基础设施制定的一个标准,这个标准包含了公钥证书的标准格式。 一个X.509证书(简称SSL证书)实际上就是一个经过编码的ASN.1(Abstract Syntax Notation One,抽象语法表示法/1)格式的电子文档。ASN.1既是一个标准,也是一种表示法,它描述了表示电信以及计算机网络数据的规则和结构。 X.509证书可以使用多种格式编码,其中一种编码格式是BER(Basic Encoding Rules,基本编码规则)。BER格式指定了一种自解释并且自定义的格式用于对ASN.1数据结构进行编码,而DER格式则是BER的一个子集。DER只提供了一种编码ASN.1值的方法,这种方法被广泛地应用于密码学当中,尤其是对X.509证书进行加密。 SSL证书可以以多种不同的格式保存,其中一种是PEM(Privacy Enhanced Email,隐私增强邮件)格式,这种格式会对DER格式的X.509证书实施Base64编码,并且这种格式的文件都以-----BEGIN CERTIFICATE-----开头,以-----END CERTIFICATE-----结尾(除了用作文件格式之外,PEM和此处讨论的SSL证书关系并不大)。 在此之后,程序通过调用crypto/rsa标准库中的GenerateKey函数生成了一个RSA私钥: 程序创建的RSA私钥的结构里面包含了一个能够公开访问的公钥(public key),这个公钥在使用x509.CreateCertificate函数创建SSL证书的时候就会用到: derBytes, _ := x509.CreateCertificate(rand.Reader, &template, &template, ➥&pk.PublicKey, pk) CreateCertificate函数接受Certificate结构、公钥和私钥等多个参数,创建出一个经过DER编码格式化的字节切片。后续代码的意图也非常简单明了,它们首先使用encoding/pem标准库将证书编码到cert.pem文件里面: certOut, _ := os.Create("cert.pem") pem.Encode(certOut, &pem.Block{Type: "CERTIFICATE", Bytes: derBytes}) certOut.Close() 然后继续以PEM编码的方式把之前生成的密钥编码并保存到key.pem文件里面: keyOut, _ := os.Create("key.pem") pem.Encode(keyOut, &pem.Block{Type: "RSA PRIVATE KEY", Bytes: ➥x509.MarshalPKCS1PrivateKey(pk)}) keyOut.Close() 最后需要提醒的是,如果证书是由CA签发的,那么证书文件中将同时包含服务器签名以及CA签名,其中服务器签名在前,CA签名在后。
|
今日新闻 |
推荐新闻 |
CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3 |